using System;
using System.Collections.Generic;
using Common.Core;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityInput = UnityEngine.Input;


namespace Common.Input
{
    public class InputController : MonoSingleton<InputController>
    {
        /// <summary>
        /// How quickly flick velocity is accumulated with movements
        /// </summary>
        const float k_FlickAccumulationFactor = 0.8f;

        /// <summary>
        /// How far mouse must move before starting a drag
        /// </summary>
        public float dragThresholdMouse;

        /// <summary>
        /// How long before a touch can no longer be considered a tap
        /// </summary>
        public float tapTime = 0.2f;

        /// <summary>
        /// How long before a touch is considered a hold
        /// </summary>
        public float holdTime = 0.8f;

        /// <summary>
        /// Flick movement threshold
        /// </summary>
        public float flickThreshold = 2f;

        /// <summary>
        /// Mouse button info
        /// </summary>
        MouseButtonInfo m_MouseInfo;

        /// <summary>
        /// Tracks if any of the mouse buttons were pressed this frame
        /// </summary>
        public bool mouseButtonPressedThisFrame { get; private set; }

        /// <summary>
        /// Tracks if the mouse moved this frame
        /// </summary>
        public bool mouseMovedOnThisFrame { get; private set; }

        /// <summary>
        /// Current mouse pointer info
        /// </summary>
        public PointerInfo basicMouseInfo { get; private set; }

        /// <summary>
        /// Event called when a pointer press is detected
        /// </summary>
        public event Action<PointerActionInfo> pressed;

        /// <summary>
        /// Event called when a pointer is released
        /// </summary>
        public event Action<PointerActionInfo> released;

        /// <summary>
        /// Event called when a pointer is tapped
        /// </summary>
        public event Action<PointerActionInfo> tapped;

        /// <summary>
        /// Event called when a drag starts
        /// </summary>
        public event Action<PointerActionInfo> startedDrag;

        /// <summary>
        /// Event called when a pointer is dragged
        /// </summary>
        public event Action<PointerActionInfo> dragged;

        /// <summary>
        /// Event called when a pointer starts a hold
        /// </summary>
        public event Action<PointerActionInfo> startedHold;

        /// <summary>
        /// Event called whenever the mouse is moved
        /// </summary>
        public event Action<PointerInfo> mouseMoved;

        protected override void Awake()
        {
            base.Awake();
            m_MouseInfo = new MouseButtonInfo()
            {
                currentPosition = UnityInput.mousePosition,
                mouseButtonId = 0
            };
            basicMouseInfo = new MouseCursorInfo {currentPosition = UnityInput.mousePosition};
        }

        void Update()
        {
            if (basicMouseInfo != null)
            {
                // Mouse was detected as present
                UpdateMouse();
            }
        }

        /// <summary>
        /// Perform logic to update mouse/pointing device
        /// </summary>
        void UpdateMouse()
        {
            basicMouseInfo.previousPosition = basicMouseInfo.currentPosition;
            basicMouseInfo.currentPosition = UnityInput.mousePosition;
            basicMouseInfo.delta = basicMouseInfo.currentPosition - basicMouseInfo.previousPosition;
            mouseMovedOnThisFrame = basicMouseInfo.delta.sqrMagnitude >= Mathf.Epsilon;
            mouseButtonPressedThisFrame = false;
            // Move event
            if (basicMouseInfo.delta.sqrMagnitude > Mathf.Epsilon)
            {
                if (mouseMoved != null)
                {
                    mouseMoved(basicMouseInfo);
                }
            }
           
            m_MouseInfo.delta = basicMouseInfo.delta;
            m_MouseInfo.previousPosition = basicMouseInfo.previousPosition;
            m_MouseInfo.currentPosition = basicMouseInfo.currentPosition;
            // Button events
            if (UnityInput.GetMouseButton(m_MouseInfo.mouseButtonId))
            {
                if (!m_MouseInfo.isDown)
                {
                    // First press
                    mouseButtonPressedThisFrame = true;
                    m_MouseInfo.isDown = true;
                    m_MouseInfo.startPosition = UnityInput.mousePosition;
                    m_MouseInfo.startTime = Time.realtimeSinceStartup;
                    m_MouseInfo.startedOverUI = EventSystem.current.IsPointerOverGameObject();

                    // Reset some stuff
                    m_MouseInfo.totalMovement = 0;
                    m_MouseInfo.isDrag = false;
                    m_MouseInfo.wasHold = false;
                    m_MouseInfo.isHold = false;
                    m_MouseInfo.flickVelocity = Vector2.zero;

                    if (pressed != null)
                    {
                        pressed(m_MouseInfo);
                    }
                }
                else
                {
                    float moveDist = m_MouseInfo.delta.magnitude;
                    // Dragging?
                    m_MouseInfo.totalMovement += moveDist;
//                    Debug.Log($"totalMovement : { m_MouseInfo.totalMovement} moveDist : {moveDist}");
                    if (m_MouseInfo.totalMovement > dragThresholdMouse)
                    {
                        //drag
                        bool wasDrag = m_MouseInfo.isDrag;
                        m_MouseInfo.isDrag = true;
                        if (m_MouseInfo.isHold)
                        {
                            m_MouseInfo.wasHold = m_MouseInfo.isHold;
                            m_MouseInfo.isHold = false;
                        }

                        //Did it just start now?
                        if (!wasDrag)
                        {
                            if (startedDrag != null)
                            {
                                startedDrag(m_MouseInfo);
                            }
                        }

                        if (dragged != null)
                        {
                            dragged(m_MouseInfo);
                        }

                        // Flick?
                        if (moveDist > flickThreshold)
                        {
                            m_MouseInfo.flickVelocity =
                                (m_MouseInfo.flickVelocity * (1 - k_FlickAccumulationFactor)) +
                                (m_MouseInfo.delta * k_FlickAccumulationFactor);
                        }
                        else
                        {
                            m_MouseInfo.flickVelocity = Vector2.zero;
                        }
                    }
                    else
                    {
                        // Stationary?
                        if (!m_MouseInfo.isHold && !m_MouseInfo.isDrag &&
                            Time.realtimeSinceStartup - m_MouseInfo.startTime >= holdTime)
                        {
                            m_MouseInfo.isHold = true;
                            if (startedHold != null)
                            {
                                startedHold(m_MouseInfo);
                            }
                        }
                    }
                }
            }
            else // Mouse button not up
            {
                if (m_MouseInfo.isDown) // Released
                {
                    m_MouseInfo.isDown = false;
                    if (m_MouseInfo.isDrag && Time.realtimeSinceStartup - m_MouseInfo.startTime < tapTime)
                    {
                        if (tapped != null)
                        {
                            tapped(m_MouseInfo);
                        }
                    }

                    if (released != null)
                    {
                        released(m_MouseInfo);
                    }
                }
            }
        }
    }
}